<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
    <style>
        video {
            width: 320px;
        }
    </style>
</head>
<body>
    <video id="localVideo" autoplay playsinline></video>
    <video id="remoteVideo" autoplay playsinline></video>

    <div>
        <button id="startBtn" onclick="startHandle()">打开本地视频</button>
        <button id="callBtn" onclick="callHandle()">建立连接</button>
        <button id="hangupBtn" onclick="hangupHandle()">断开连接</button>
    </div>
    <!-- 适配各浏览器 API 不统一的脚本 -->
</body>
</html>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script>
    // 本地流和远端流
    let localStream;
    let remoteStream;

    // 本地和远端连接对象
    let localPeerConnection;
    let remotePeerConnection;

    // 本地视频和远端视频
    const localVideo = document.getElementById('localVideo');
    const remoteVideo = document.getElementById('remoteVideo');

    const startBtn = document.getElementById('startBtn')
    const callBtn = document.getElementById('callBtn')
    const hangupBtn = document.getElementById('hangupBtn')

    // 设置约束
    const mediaStreamConstraints = {
        video: true,
        audio: true
    }

    // 设置仅交换视频
    const offerOptions = {
        offerToReceiveVideo: 1
    }
    function startHandle() {
        startBtn.disabled = true;
        // 1.获取本地音视频流
        // 调用 getUserMedia API 获取音视频流
        navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
            .then(gotLocalMediaStream)
            .catch((err) => {
                console.log('getUserMedia 错误', err);
            });
    }

    function callHandle() {
        callBtn.disabled = true;
        hangupBtn.disabled = false;

        // 视频轨道
        const videoTracks = localStream.getVideoTracks();
        // 音频轨道
        const audioTracks = localStream.getAudioTracks();
        // 判断视频轨道是否有值
        if (videoTracks.length > 0) {
            console.log(`使用的设备为: ${videoTracks[0].label}.`);
        }
        // 判断音频轨道是否有值
        if (audioTracks.length > 0) {
            console.log(`使用的设备为: ${audioTracks[0].label}.`);
        }
        const servers = null;

        // 创建 RTCPeerConnection 对象
        localPeerConnection = new RTCPeerConnection(servers);
        // 监听返回的 Candidate
        localPeerConnection.addEventListener('icecandidate', handleConnection);
        // 监听 ICE 状态变化
        localPeerConnection.addEventListener('iceconnectionstatechange', handleConnectionChange)

        remotePeerConnection = new RTCPeerConnection(servers);
        remotePeerConnection.addEventListener('icecandidate', handleConnection);
        remotePeerConnection.addEventListener('iceconnectionstatechange', handleConnectionChange);
        remotePeerConnection.addEventListener('track', gotRemoteMediaStream);

        // 将音视频流添加到 RTCPeerConnection 对象中
        // 注意：新的协议中已经不再推荐使用 addStream 方法来添加媒体流，应使用 addTrack 方法
        // localPeerConnection.addStream(localStream);
        // 遍历本地流的所有轨道
        localStream.getTracks().forEach((track) => {
            localPeerConnection.addTrack(track, localStream)
        })

        // 2.交换媒体描述信息
        localPeerConnection.createOffer()
        .then(createdOffer).catch((err) => {
            console.log('createdOffer 错误', err);
        });
    }

    function hangupHandle() {
        // 关闭连接并设置为空
        localPeerConnection.close();
        remotePeerConnection.close();
        localPeerConnection = null;
        remotePeerConnection = null;
        hangupBtn.disabled = true;
        callBtn.disabled = false;
    }

    // getUserMedia 获得流后，将音视频流展示并保存到 localStream
    function gotLocalMediaStream(mediaStream) {
        localVideo.srcObject = mediaStream; 
        localStream = mediaStream; 
        callBtn.disabled = false;
    }

    function createdOffer(description) {
        console.log(`本地创建offer返回的sdp:\n${description.sdp}`)
        // 本地设置描述并将它发送给远端
        // 将 offer 保存到本地
        localPeerConnection.setLocalDescription(description) 
            .then(() => {
                console.log('local 设置本地描述信息成功');
            }).catch((err) => {
                console.log('local 设置本地描述信息错误', err)
            });
        // 远端将本地给它的描述设置为远端描述
        // 远端将 offer 保存
        remotePeerConnection.setRemoteDescription(description) 
            .then(() => { 
                console.log('remote 设置远端描述信息成功');
            }).catch((err) => {
                console.log('remote 设置远端描述信息错误', err);
            });
        // 远端创建应答 answer
        remotePeerConnection.createAnswer() 
            .then(createdAnswer)
            .catch((err) => {
                console.log('远端创建应答 answer 错误', err);
            });
    }

    function createdAnswer(description) {
        console.log(`远端应答Answer的sdp:\n${description.sdp}`)
        // 远端设置本地描述并将它发给本地
        // 远端保存 answer
        remotePeerConnection.setLocalDescription(description)
            .then(() => { 
                console.log('remote 设置本地描述信息成功');
            }).catch((err) => {
                console.log('remote 设置本地描述信息错误', err);
            });
        // 本地将远端的应答描述设置为远端描述
        // 本地保存 answer
        localPeerConnection.setRemoteDescription(description) 
            .then(() => { 
                console.log('local 设置远端描述信息成功');
            }).catch((err) => {
                console.log('local 设置远端描述信息错误', err);
            });
    }

    // 3.端与端建立连接
    function handleConnection(event) {
        // 获取到触发 icecandidate 事件的 RTCPeerConnection 对象 
        // 获取到具体的Candidate
        const peerConnection = event.target;
        const iceCandidate = event.candidate;

        if (iceCandidate) {
            // 创建 RTCIceCandidate 对象
            const newIceCandidate = new RTCIceCandidate(iceCandidate);
            // 得到对端的 RTCPeerConnection
            const otherPeer = getOtherPeer(peerConnection);

            // 将本地获得的 Candidate 添加到远端的 RTCPeerConnection 对象中
            // 为了简单，这里并没有通过信令服务器来发送 Candidate，直接通过 addIceCandidate 来达到互换 Candidate 信息的目的
            otherPeer.addIceCandidate(newIceCandidate)
                .then(() => {
                    handleConnectionSuccess(peerConnection);
                }).catch((error) => {
                    handleConnectionFailure(peerConnection, error);
                });
        }
    }

    // 4.显示远端媒体流
    function gotRemoteMediaStream(event) {
        if (remoteVideo.srcObject !== event.streams[0]) {
            remoteVideo.srcObject = event.streams[0];
            remoteStream = event.streams[0];
            console.log('remote 开始接受远端流')
        }
    }
    function handleConnectionChange(event) {
        const peerConnection = event.target;
        console.log('ICE state change event: ', event);
        console.log(`${getPeerName(peerConnection)} ICE state: ` + `${peerConnection.iceConnectionState}.`);
    }

    function handleConnectionSuccess(peerConnection) {
        console.log(`${getPeerName(peerConnection)} addIceCandidate 成功`);
    }

    function handleConnectionFailure(peerConnection, error) {
        console.log(`${getPeerName(peerConnection)} addIceCandidate 错误:\n`+ `${error.toString()}.`);
    }

    function getPeerName(peerConnection) {
        return (peerConnection === localPeerConnection) ? 'localPeerConnection' : 'remotePeerConnection';
    }

    function getOtherPeer(peerConnection) {
        return (peerConnection === localPeerConnection) ? remotePeerConnection : localPeerConnection;
    }

</script>
